package com.yyw.gulimall.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yyw.common.constant.cart.CartConstant;
import com.yyw.common.exception.FeignCallException;
import com.yyw.common.exception.RRException;
import com.yyw.common.utils.R;
import com.yyw.gulimall.cart.feign.CartFeignService;
import com.yyw.gulimall.cart.interceptor.CartInterceptor;
import com.yyw.gulimall.cart.service.CartService;
import com.yyw.gulimall.cart.to.SkuInfoTo;
import com.yyw.gulimall.cart.to.UserInfoTo;
import com.yyw.gulimall.cart.vo.CartItemVo;
import com.yyw.gulimall.cart.vo.CartVo;
import com.yyw.gulimall.cart.vo.CheckCartVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @Classname CartServiceImpl
 * @Description 购物车实现类
 * @Date 2020/12/21 16:39
 * @Created yyw
 */
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CartServiceImpl implements CartService {

    private final StringRedisTemplate redisTemplate;

    private final CartFeignService cartFeignService;

    private final ThreadPoolExecutor executor;

    /**
     * 添加商品购物车
     *
     * @param skuId
     * @param num
     * @return
     */
    @Override
    public CartItemVo addShoppingCart(String skuId, String num) {
        // 1. 根据拦截器获取是操作当前登录用户还是操作临时用户
        BoundHashOperations<String, Object, Object> ops = getCartKeyByThreadLocal();

        // 2. 判断redis 里是否存在，否则就是新增数量
        String redisData = (String) ops.get(skuId);
        if (!Objects.isNull(redisData)) {
            // 新增数量操作
            CartItemVo itemVo = JSON.parseObject(redisData, CartItemVo.class);
            itemVo.setCount(itemVo.getCount() + Integer.parseInt(num));
            String jsonString = JSON.toJSONString(itemVo);
            ops.put(skuId, jsonString);
            return itemVo;
        }

        CartItemVo itemVo = new CartItemVo();
        itemVo.setCount(Integer.parseInt(num));

        // 3. 根据SkuId 查询数据
        CompletableFuture<Void> getSkuInfo = CompletableFuture.runAsync(() -> {
            // 发送远程请求，获取Sku详情
            R r = cartFeignService.info(Long.parseLong(skuId));
            if (!Objects.equals(r.get("code"), 0)) {
                String msg = (String) r.get("msg");
                log.error(msg);
                throw new FeignCallException("远程调用商品服务出现异常");
            }
            SkuInfoTo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoTo>() {
            });
            itemVo.setCheck(true);
            itemVo.setSkuId(Long.parseLong(skuId));
            itemVo.setSkuImg(skuInfo.getSkuDefaultImg());
            itemVo.setSkuPrice(skuInfo.getPrice());
            itemVo.setSkuTitle(skuInfo.getSkuTitle());
        }, executor);

        // 4.根据SkuId 查询销售属性组合
        CompletableFuture<Void> getAttrs = CompletableFuture.runAsync(() -> {
            R r = cartFeignService.getAttr(skuId);
            if (!Objects.equals(r.get("code"), 0)) {
                String msg = (String) r.get("msg");
                log.error(msg);
                throw new FeignCallException("远程调用商品服务出现异常");
            }
            List<String> data = r.getData("data", new TypeReference<List<String>>() {
            });
            itemVo.setAttrValues(data);
        }, executor);

        // 5. 等待完成
        try {
            CompletableFuture.allOf(getSkuInfo, getAttrs).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 6. 添加至Redis
        String jsonString = JSON.toJSONString(itemVo);
        ops.put(skuId, jsonString);

        return itemVo;

    }

    @Override
    public CartItemVo getCartBySkuId(String skuId) {
        BoundHashOperations<String, Object, Object> ops = getCartKeyByThreadLocal();
        String o = (String) ops.get(skuId);
        if (!Objects.isNull(o)) {
            return JSON.parseObject(o, CartItemVo.class);
        }
        return new CartItemVo();
    }

    @Override
    public CartVo getCartList() {
        CartVo cartVo = new CartVo();
        boolean hasResult = false;  // 标识是否临时用户存在数据

        // 1. 获取ThreadLocal中的用户，判断是登录还是未登录
        ThreadLocal<UserInfoTo> local = CartInterceptor.threadLocal;
        UserInfoTo user = local.get();

        // 2. 不管是临时还是已登录，都先看看是否有临时的购物车
        String tempKey = CartConstant.CART_REDIS_KEY + user.getUserKey();

        // 3. 填充到结果集
        List<CartItemVo> redisData = getRedisDataByUser(tempKey);
        if (!CollectionUtils.isEmpty(redisData)) {
            hasResult = true;
            cartVo.getItems().addAll(redisData);
        }

        // 4. 如果已登录，也要添加数据
        if (!Objects.equals(null, user.getUserId())) {
            String userKey = CartConstant.CART_REDIS_KEY + user.getUserId().toString();
            List<CartItemVo> data = getRedisDataByUser(userKey);
            if (!CollectionUtils.isEmpty(data)) {
                cartVo.getItems().addAll(data);
            }
            if (hasResult) {
                // 合并购物车
                redisData.forEach(e -> {
                    addShoppingCart(e.getSkuId().toString(), e.getCount().toString());
                });
                // 删除临时数据
                redisTemplate.delete(tempKey);
            }
        }
        return cartVo;
    }

    /**
     * 购物车列表，修改购物项的属性
     * @param cartVo
     */
    @Override
    public void updateCart(CheckCartVo cartVo) {
        // 1. 获取购物车
        BoundHashOperations<String, Object, Object> ops = getCartKeyByThreadLocal();
        if (Objects.equals(null, cartVo.getSkuId())) {
            throw new RRException("SkuID参数缺少");
        }
        // 2. 获取item项
        String o = (String) ops.get(cartVo.getSkuId());
        try {
            CartItemVo itemVo = JSON.parseObject(o, CartItemVo.class);
            if (!Objects.equals(null, cartVo.getIsChecked())) {
                itemVo.setCheck(Objects.equals("1", cartVo.getIsChecked()) ? true : false);
            }
            if (!Objects.equals(null, cartVo.getCount())) {
                itemVo.setCount(cartVo.getCount());
            }
            // 3. 修改数据
            String jsonString = JSON.toJSONString(itemVo);
            ops.put(cartVo.getSkuId(), jsonString);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RRException("实体类转换CartItemVo类失败:" + o);
        }
    }

    /**
     * 购物车列表，删除购物项
     * @param skuid
     */
    @Override
    public void delItem(String skuid) {
        BoundHashOperations<String, Object, Object> ops = getCartKeyByThreadLocal();
        ops.delete(skuid);
    }


    /**
     * 根据用户的Key 获取值
     *
     * @param key
     */
    private List<CartItemVo> getRedisDataByUser(String key) {
        BoundHashOperations<String, Object, Object> userKeyOps = redisTemplate.boundHashOps(key);
        if (userKeyOps.size() > 0) {
            // 获取所有的 V，返回List<CartItemVo>
            List<Object> values = userKeyOps.values();
            return values.stream().map(e -> {
                String str = (String) e;
                CartItemVo itemVo = JSON.parseObject(str, CartItemVo.class);
                return itemVo;
            }).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 根据拦截器获取是操作当前登录用户还是操作临时用户
     *
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCartKeyByThreadLocal() {
        String key = getKey();
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(key);
        return ops;
    }

    /**
     * 根据当前/离线/在线用户获取key
     *
     * @return
     */
    private String getKey() {
        ThreadLocal<UserInfoTo> threadLocal = CartInterceptor.threadLocal;
        UserInfoTo userInfo = threadLocal.get();
        String key = Objects.equals(null, userInfo.getUserId()) ? CartConstant.CART_REDIS_KEY + userInfo.getUserKey()
                : CartConstant.CART_REDIS_KEY + userInfo.getUserId();
        return key;
    }
}
